package org.song.util.snowFlake;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;

public class SnowFlake {

    private final long unusedBits = 1L;
    private final long timestampBits = 41L;
    private final long datacenterIdBits = 5L;
    private final long workerIdBits = 5L;
    private final long sequenceBits = 12L;
    private final long maxDatacenterId = 31L;
    private final long maxWorkerId = 31L;
    private final long maxSequence = 4095L;
    private final long timestampShift = 22L;
    private final long datacenterIdShift = 17L;
    private final long workerIdShift = 12L;
    private final long epoch = 1451606400000L;
    private final long datacenterId;
    private final long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    private final AtomicLong waitCount = new AtomicLong(0L);

    public synchronized long nextId() {
        long currTimestamp = this.timestampGen();
        if (currTimestamp < this.lastTimestamp) {
            throw new IllegalStateException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", this.lastTimestamp - currTimestamp));
        } else {
            if (currTimestamp == this.lastTimestamp) {
                this.sequence = this.sequence + 1L & 4095L;
                if (this.sequence == 0L) {
                    currTimestamp = this.waitNextMillis(currTimestamp);
                }
            } else {
                this.sequence = 0L;
            }

            this.lastTimestamp = currTimestamp;
            return currTimestamp - 1451606400000L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
        }
    }

    public SnowFlake(long datacenterId, long workerId) {
        if (datacenterId <= 31L && datacenterId >= 0L) {
            if (workerId <= 31L && workerId >= 0L) {
                this.datacenterId = datacenterId;
                this.workerId = workerId;
            } else {
                throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", 31L));
            }
        } else {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", 31L));
        }
    }

    public long getWaitCount() {
        return this.waitCount.get();
    }

    protected long waitNextMillis(long currTimestamp) {
        this.waitCount.incrementAndGet();

        while(currTimestamp <= this.lastTimestamp) {
            currTimestamp = this.timestampGen();
        }

        return currTimestamp;
    }

    protected long timestampGen() {
        return System.currentTimeMillis();
    }

    public String toString() {
        return "Snowflake Settings [timestampBits=41, datacenterIdBits=5, workerIdBits=5, sequenceBits=12, epoch=1451606400000, datacenterId=" + this.datacenterId + ", workerId=" + this.workerId + "]";
    }

    public long getEpoch() {
        this.getClass();
        return 1451606400000L;
    }

    public long[] parseId(long id) {
        long[] arr = new long[5];
        arr[4] = (id & this.diode(1L, 41L)) >> 22;
        arr[0] = arr[4] + 1451606400000L;
        arr[1] = (id & this.diode(42L, 5L)) >> 17;
        arr[2] = (id & this.diode(47L, 5L)) >> 12;
        arr[3] = id & this.diode(52L, 12L);
        return arr;
    }

    public String formatId(long id) {
        long[] arr = this.parseId(id);
        String tmf = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")).format(new Date(arr[0]));
        return String.format("%s, #%d, @(%d,%d)", tmf, arr[3], arr[1], arr[2]);
    }

    private long diode(long offset, long length) {
        int lb = (int)(64L - offset);
        int rb = (int)(64L - (offset + length));
        return -1L << lb ^ -1L << rb;
    }

}
